﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;

namespace MugenInjection
{
    /// <summary>
    ///     Represents a extensions method for the <see cref="IReflectionAccessProvider" />
    /// </summary>
    public static class ReflectionExtension
    {
        #region Public method

        /// <summary>
        ///     Invokes the constructor reflected by the instance that has the specified parameters.
        /// </summary>
        /// <param name="constructorInfo">
        ///     The specified <see cref="ConstructorInfo" />.
        /// </param>
        /// <param name="parameters">An argument list for the invoked constructor. This is an array of objects with the same number, order, and type as the parameters of the method to be invoked.</param>
        /// <returns>An instance of object.</returns>
        public static object CreateInstance(
#if !NET2
this 
#endif
ConstructorInfo constructorInfo, params object[] parameters)
        {
            Validate.ArgumentNotNull(constructorInfo, "constructorInfo");
            if (constructorInfo.IsStatic)
                throw new ArgumentException("The constructor cannot be static.");
            return InjectorUtils.ReflectionAccessProvider.GetActivatorDelegate(constructorInfo)(parameters);
        }

        /// <summary>
        ///     Invokes the method represented by the specified <see cref="MethodInfo" />, using the specified parameters.
        /// </summary>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <param name="target">The object on which to invoke the method. If a method is static, this argument is ignored.</param>
        /// <param name="parameters">An argument list for the invoked method. This is an array of objects with the same number, order, and type as the parameters of the method to be invoked.</param>
        /// <returns></returns>
        public static object InvokeFast<TTarget>(
#if !NET2
this 
#endif
MethodInfo methodInfo, TTarget target, params object[] parameters) where TTarget : class
        {
            Validate.ArgumentNotNull(methodInfo, "methodInfo");
            if (target == null && !methodInfo.IsStatic)
                throw new ArgumentException("To call a non static method need to specify the target.");
            return InjectorUtils.ReflectionAccessProvider.GetInvokeMethodDelegate<TTarget>(methodInfo)(target,
                                                                                                       parameters);
        }

        /// <summary>
        ///     Invokes the method represented by the specified <see cref="MethodInfo" />, using the specified parameters.
        /// </summary>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <param name="target">The object on which to invoke the method. If a method is static, this argument is ignored.</param>
        /// <param name="parameters">An argument list for the invoked method. This is an array of objects with the same number, order, and type as the parameters of the method to be invoked.</param>
        /// <returns></returns>
        public static object InvokeFast(
#if !NET2
this 
#endif
MethodInfo methodInfo, object target, params object[] parameters)
        {
            Validate.ArgumentNotNull(methodInfo, "methodInfo");
            if (target == null && !methodInfo.IsStatic)
                throw new ArgumentException("To call a non static method need to specify the target.");
            return InjectorUtils.ReflectionAccessProvider.GetInvokeMethodDelegate<object>(methodInfo)(target, parameters);
        }

        /// <summary>
        ///     Invokes the method represented by the specified <see cref="MethodInfo" />, using the specified parameters.
        /// </summary>
        /// <param name="methodInfo">
        ///     The specified <see cref="MethodInfo" />
        /// </param>
        /// <param name="target">The object on which to invoke the method. If a method is static, this argument is ignored.</param>
        /// <param name="parameters">An argument list for the invoked method. This is an array of objects with the same number, order, and type as the parameters of the method to be invoked.</param>
        /// <returns></returns>
        public static object InvokeFast<TTarget>(
#if !NET2
this 
#endif
MethodInfo methodInfo, ref TTarget target, params object[] parameters)
        {
            Validate.ArgumentNotNull(methodInfo, "methodInfo");
            if (target == null && !methodInfo.IsStatic)
                throw new ArgumentException("To call a non static method need to specify the target.");
            return
                InjectorUtils.ReflectionAccessProvider.GetInvokeMethodValueTypeDelegate<TTarget>(methodInfo)(
                    ref target, parameters);
        }

        /// <summary>
        ///     Sets the value of the field or property supported by the given object.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <param name="target">The object whose field or property value will be set.</param>
        /// <param name="value">The value to assign to the member.</param>
        public static void SetValueFast<TTarget>(
#if !NET2
this 
#endif
MemberInfo memberInfo, TTarget target, object value)
            where TTarget : class
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            InjectorUtils.ReflectionAccessProvider.GetAssignMemberDelegate<TTarget>(memberInfo)(target, value);
        }

        /// <summary>
        ///     Sets the value of the field or property supported by the given object.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <param name="target">The object whose field or property value will be set.</param>
        /// <param name="value">The value to assign to the member.</param>
        public static void SetValueFast<TTarget>(
#if !NET2
this 
#endif
MemberInfo memberInfo, ref TTarget target, object value)
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            InjectorUtils.ReflectionAccessProvider.GetAssignValueMemberDelegate<TTarget>(memberInfo)(ref target, value);
        }

        /// <summary>
        ///     Returns the value of a field or property supported by a given object.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <typeparam name="T">
        ///     The type of <see cref="MemberInfo" /> value to get.
        /// </typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <param name="target">The object whose field value will be returned.</param>
        /// <returns>An object containing the value of the field reflected by this instance.</returns>
        public static T GetValueFast<TTarget, T>(
#if !NET2
this 
#endif
MemberInfo memberInfo, TTarget target)
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            return InjectorUtils.ReflectionAccessProvider.GetGetterMemberDelegate<TTarget, T>(memberInfo)(target);
        }

        /// <summary>
        ///     Returns the value of a field or property supported by a given object.
        /// </summary>
        /// <typeparam name="TTarget">Type of the target.</typeparam>
        /// <param name="memberInfo">
        ///     The specified <see cref="MemberInfo" />.
        /// </param>
        /// <param name="target">The object whose field value will be returned.</param>
        /// <returns>An object containing the value of the field reflected by this instance.</returns>
        public static object GetValueFast<TTarget>(
#if !NET2
this 
#endif
MemberInfo memberInfo, TTarget target)
        {
            Validate.ArgumentNotNull(memberInfo, "memberInfo");
            return InjectorUtils.ReflectionAccessProvider.GetGetterMemberDelegate<TTarget, object>(memberInfo)(target);
        }

        /// <summary>
        ///     Returns a weak-reference version of a delegate.
        /// </summary>
        /// <param name="originalAction">The delegate to convert to weak referencing.</param>
        /// <returns>A weak referencing delegate.</returns>
        public static T ConvertToWeakDelegate<T>(T originalAction) where T : class
        {
            return ConvertToWeakDelegate(originalAction, null);
        }

        /// <summary>
        ///     Returns a weak-reference version of a delegate.
        /// </summary>
        /// <param name="originalAction">The delegate to convert to weak referencing.</param>
        /// <param name="unsubcribeDelegate">The unregister action to invoke if the target is garbage collected.</param>
        /// <returns>A weak referencing delegate.</returns>
        public static T ConvertToWeakDelegate<T>(T originalAction, Action<T> unsubcribeDelegate) where T : class
        {
            var @delegate = originalAction as Delegate;
            if (@delegate == null)
                throw new ArgumentNullException("originalAction", "Parameter can only be a delegate.");
            Action<Delegate> uns = null;
            if (unsubcribeDelegate != null)
                uns = d => unsubcribeDelegate((T)((object)d));
            return
                InjectorUtils.ReflectionAccessProvider.ConvertToWeakDelegate(@delegate, uns)
                as T;
        }

        /// <summary>
        /// Finds the weak-delegate use a original delegate in the specified array of delegate.
        /// </summary>
        /// <param name="originalAction">The original delegate to find weak delegate.</param>
        /// <param name="delegates">The specified array of delegate</param>
        /// <returns>An instance of delegate.</returns>
        public static T FindWeakDelegateByOriginalDelegate<T>(T originalAction, IList<Delegate> delegates) where T : class
        {
            var @delegate = originalAction as Delegate;
            if (@delegate == null)
                throw new ArgumentNullException("originalAction", "Parameter can only be a delegate.");
            return InjectorUtils.ReflectionAccessProvider.FindWeakDelegateByOriginalDelegate(@delegate, delegates) as T;
        }

        /// <summary>
        ///     Converts the Func{object[], object} to the specified delegate type.
        /// </summary>
        /// <param name="proxyDelegate">The specified proxy method.</param>
        /// <param name="delegateType">The specified delegate type.</param>
        /// <returns>An instance of delegate type.</returns>
        public static Delegate ConvertToProxyDelegate(Func<object[], object> proxyDelegate, Type delegateType)
        {
            Validate.ArgumentNotNull(proxyDelegate, "proxyDelegate");
            Validate.ArgumentNotNull(delegateType, "delegateType");
            return InjectorUtils.ReflectionAccessProvider.ConvertToProxyDelegate(proxyDelegate, delegateType);
        }

        #endregion
    }
}